package controls
{
	import com.maclema.mysql.MySqlToken;
	import com.maclema.mysql.ResultSet;
	
	import controls.events.ServiceEvent;
	
	import data.Degree;
	import data.DegreeType;
	import data.Professor;
	import data.User;
	
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;
	
	[Event(name="serviceConnected", source="controls.events.ServiceEvent")];
	[Event(name="majorsLoaded", source="controls.events.ServiceEvent")];
	[Event(name="minorsLoaded", source="controls.events.ServiceEvent")];
	[Event(name="certificatesLoaded", source="controls.events.ServiceEvent")];
	
	public class DataHub extends EventDispatcher
	{
		// --------------- Singleton Implementation ----------------- //
		private static var _instance:DataHub;
		
		public static function get instance():DataHub
		{
			if(_instance == null)
				_instance = new DataHub();
			return _instance;
		}
		
		public function DataHub()
		{
			if(_instance != null)
				trace("Error: DataHub is a singleton.");
				
			initComponents();
		}
		// --------------- END Singleton Implementation ----------------- //
		
		// ------------------- Private Variables ------------------- //
		private var requestor:DataRequestor;
		private var _user:User;
		private var _currentSemester:String;
		
		private var _degreePrograms:Dictionary;
		private var _professors:Dictionary;
		private var pendingCallbacks:Dictionary;
		// ------------------- END Private Variables ------------------- //
		
		// --------------- Initialization and Setup ----------------- //
		private function initComponents():void
		{
			requestor = DataRequestor.instance;
			requestor.addEventListener(ServiceEvent.SERVICE_CONNECTED, onConnection)
			
			professors = new Dictionary();
			currentSemester = "Summer 2009";
			
			degreePrograms = new Dictionary();
			degreePrograms[DegreeType.MAJOR] = new Dictionary();
			degreePrograms[DegreeType.MINOR] = new Dictionary();
			degreePrograms[DegreeType.CERTIFICATE] = new Dictionary();
			
			pendingCallbacks = new Dictionary();
			pendingCallbacks['degrees'] = new Dictionary();
			pendingCallbacks['professor'] = new Dictionary();
			
			if(requestor.connected)
			{
				requestor.getMajors(parseMajors);
				requestor.getMinors(parseMinors);
				requestor.getCertificates(parseCertificates);
			}
		}
		
		private function onConnection(evt:ServiceEvent):void
		{
			for (var key:String in majors)
			
			if(key != "") return;
			
			requestor.getMajors(parseMajors);
			requestor.getMinors(parseMinors);
			requestor.getCertificates(parseCertificates);
			
			this.dispatchEvent(new ServiceEvent(ServiceEvent.SERVICE_CONNECTED, true));
		}
		// --------------- END Initialization and Setup ----------------- //
		
		// ------------- User Verification and Addition --------------- //
		public function checkUser(username:String, password:String):void
		{
			requestor.checkUser(username, password, parseUserVerification);
		}
		
		private function parseUserVerification(sqlData:Object, token:MySqlToken):void
		{
			var rs:ResultSet = sqlData as ResultSet;
			var username:String = token.username;
			var password:String = token.password;
			for each(var row:Object in rs.getRows())
			{
				if(row.userName == username && row.password == password)
				{
					this.user = new User(row);
					this.dispatchEvent(new ServiceEvent(ServiceEvent.USER_VERIFIED));
					return;
				}
			}
			this.dispatchEvent(new ServiceEvent(ServiceEvent.USER_DENIED));	
		}
		
		private function checkUserUpdate(sqlData:Object, token:MySqlToken):void
		{
			if(sqlData is String && String(sqlData).indexOf("Error") != -1)
				this.dispatchEvent(new ServiceEvent(ServiceEvent.USER_DENIED));
			else
				this.dispatchEvent(new ServiceEvent(ServiceEvent.USER_VERIFIED));
		}
		
		private function checkUserAddition(sqlData:Object, token:MySqlToken):void
		{
			if(sqlData is String && String(sqlData).indexOf("Error") != -1)
				this.dispatchEvent(new ServiceEvent(ServiceEvent.USER_DENIED));
			else
				this.dispatchEvent(new ServiceEvent(ServiceEvent.USER_VERIFIED));
		}
		// ------------- END User Verification and Addition --------------- //
		
		// --------------- Data Parsing ----------------- //
		private function parseMajors(sqlData:Object, token:MySqlToken):void
		{
			var rs:ResultSet = sqlData as ResultSet;
			for each(var row:Object in rs.getRows())
				majors[row.degreeName] = new Degree(row);	
			this.dispatchEvent(new ServiceEvent(ServiceEvent.MAJORS_LOADED, majors));
		}
		
		private function parseMinors(sqlData:Object, token:MySqlToken):void
		{
			var rs:ResultSet = sqlData as ResultSet;
			for each(var row:Object in rs.getRows())
				minors[row.degreeName] = new Degree(row);	
			this.dispatchEvent(new ServiceEvent(ServiceEvent.MINORS_LOADED, minors));
		}
		
		private function parseCertificates(sqlData:Object, token:MySqlToken):void
		{
			var rs:ResultSet = sqlData as ResultSet;
			for each(var row:Object in rs.getRows())
				certificates[row.degreeName] = new Degree(row);	
			this.dispatchEvent(new ServiceEvent(ServiceEvent.CERTIFICATES_LOADED, certificates));
		}
		// --------------- END Data Parsing ----------------- //
		
		// --------------- Utility Functions ----------------- //
		public function updateDegree(degree:Degree):void
		{
			if(user.degrees[degree.type].hasOwnProperty(degree.name))
				user.degrees[degree.type][degree.name] = degree;
			degreePrograms[degree.type][degree.name] = degree;
			
			if(user.hasDegree(degree))
				user.updateDegree(degree);
			
			this.dispatchEvent(new ServiceEvent(ServiceEvent.PROGRAM_LOADED, degree));
		}
		
		private function dictionaryToArray(dict:Dictionary):Array
		{
			var array:Array = [];
			
			for each(var content:Object in dict)
				array.push(content); 
			
			return array;
		}
		
		private function loadUserDegrees():void
		{
			var uDegrees:Dictionary = user.degrees;
			
			loadDegrees(uDegrees[DegreeType.MAJOR]); 
			loadDegrees(uDegrees[DegreeType.MINOR]);
			loadDegrees(uDegrees[DegreeType.CERTIFICATE]);
		}
		
		private function loadDegrees(degreeDict:Dictionary):void
		{
			for each(var degree:Degree in degreeDict)
				degree.getClassInformation();
		}
		// --------------- END Utility Functions ----------------- //
		
		// ---------------- Data Access Functions ------------------ //
		public function getProfessor(professor:Professor, callback:Function):Professor
		{
			if(professors.hasOwnProperty(professor.name))
				return professors[professor.name];
			
			pendingCallbacks['professor'][professor.name] = callback;
			professor.getGPAInfo();
			
			return null;
		}
		public function addProfessor(professor:Professor):void
		{
			professors[professor.name] = professor;
		}
		
		public function getUserDegrees():Array
		{
			var uDegrees:Array = [];
			uDegrees.concat(dictionaryToArray(user.degrees[DegreeType.MAJOR]));
			uDegrees.concat(dictionaryToArray(user.degrees[DegreeType.MINOR]));
			uDegrees.concat(dictionaryToArray(user.degrees[DegreeType.CERTIFICATE]));
			return uDegrees;
		}
		
		public function getMajors():Array
		{
			return dictionaryToArray(majors);
		}
		
		public function getMinors():Array
		{
			return dictionaryToArray(minors);
		}
		
		public function getCertificates():Array
		{
			return dictionaryToArray(certificates);	
		}
		
		public function getDegree(degree:Degree, callback:Function):Degree
		{
			if(degreePrograms[degree.type].hasOwnProperty(degree.name))
				return degreePrograms[degree.type][degree.name]
			
			pendingCallbacks['degrees'][degree.name] = callback;
			degree.getClassInformation();
			
			return null;
		}
		
		public function getDegreeById(degreeId:int, callback:Function):Degree
		{
			for(var degreeType:String in degreePrograms)
				for each(var degree:Degree in degreePrograms[degreeType])
					if(degree.id == degreeId)
						return degree;
			return null;
		}
		// ---------------- END Data Access Functions ------------------ //
		
		// -------------------- Accessors ---------------------- //
		public function get currentSemester():String
		{
			return _currentSemester;
		}
		public function set currentSemester(value:String):void
		{
			_currentSemester = value;
		}
		public function get user():User
		{
			return _user;
		}
		public function set user(value:User):void
		{
			_user = value;
		}
		public function setUser(value:User, loadDegrees:Boolean = false):void
		{
			if(_user != null && _user.username == value.username)
				requestor.updateUser(value, checkUserUpdate);
			else
				requestor.addUser(value, checkUserAddition);
			
			_user = value;
			
			if(loadDegrees)
				loadUserDegrees();
		}
		
		public function get degreePrograms():Dictionary
		{
			return _degreePrograms;
		}
		public function set degreePrograms(value:Dictionary):void
		{
			_degreePrograms = value;
		}
		
		public function get majors():Dictionary
		{
			return degreePrograms[DegreeType.MAJOR];
		}
		public function set majors(value:Dictionary):void
		{
			degreePrograms[DegreeType.MAJOR] = value;
		}
		
		public function get minors():Dictionary
		{
			return degreePrograms[DegreeType.MINOR];
		}
		public function set minors(value:Dictionary):void
		{
			degreePrograms[DegreeType.MINOR] = value;
		}
		
		public function get certificates():Dictionary
		{
			return degreePrograms[DegreeType.CERTIFICATE];
		}
		public function set certificates(value:Dictionary):void
		{
			degreePrograms[DegreeType.CERTIFICATE] = value;
		}
		
		public function get professors():Dictionary
		{
			return _professors;
		}
		public function set professors(value:Dictionary):void
		{
			_professors = value;
		}
		// -------------------- END Accessors ---------------------- //

	}
}